// 
// Entify project
//  
// Author:
//       Lauri Taimila <lauri@taimila.com>
// 
// Copyright (c) 2010 Lauri Taimila
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

namespace Taimila.Entify.Sqlite
{
	/// <summary>
	/// Type mapper provides methods for mapping objects and types
	/// between C# and Sqlite types.
	/// </summary>
	internal static class SqliteTypeMapper
	{
		/// <summary>
		/// Maps values from all C# types to subset of C# types that SQLite can handle. 
		/// </summary>
		/// <param name="original">
		/// Original value <see cref="System.Object"/>
		/// </param>
		/// <returns>
		/// Given value in SQLite compatible type. <see cref="System.Object"/>
		/// </returns>
		public static object GetValueForSqliteParameter(object original)
		{
			object newObject = null;
			
			if(original == null)
			{
				return null;	
			}
			if(original is DateTime)
			{
				newObject = ((DateTime) original).ToFileTime();	
			}
			else if(original is decimal)
			{
				newObject = Convert.ToDouble(original);	
			}
			else if(original is ulong)
			{
				newObject = Convert.ToInt64(original); //FIXME: Dangerous? Should we remove support for ulong type?	
			}
			else if(original is char)
			{
				newObject = original.ToString();	
			}
			else if(original is Enum)
			{
				newObject = Enum.GetName(original.GetType(), original);	
			}
			else if(original is string || original is int || original is long || 
			        original is double || original is byte || original is float || 
			        original is short || original is bool || original is ushort || 
			        original is sbyte || original is uint)
			{
				newObject = original;	
			}
			
			// One to one relation value is replaced with it's entity identifier.
			else if(original is Entity)
			{
				return (original as Entity).GetEntityId();
			}
			
			// Serializable object is replaced with it's bytestream.
			else if(original.GetType().IsSerializable)
			{
				BinaryFormatter binaryFormatter = new BinaryFormatter();
				MemoryStream stream = new MemoryStream();
				binaryFormatter.Serialize(stream, original);
				return stream.GetBuffer();
			}
			else
			{
				newObject = original;	
			}
			
			return newObject;
		}
		
			
		/// <summary>
		/// Map C# type to SQLite column type. SQLite columns are typless in reality, but this
		/// mapping makes things clearer when developing and debugging. SQLite doesn't make
		/// any type checks when inserting data.
		/// </summary>
		/// <param name="t">
		/// C# Type <see cref="Type"/>
		/// </param>
		/// <returns>
		/// Sqlite column type <see cref="System.String"/>
		/// </returns>
		public static string GetSqliteColumnTypeFrom(Type t)
		{
			if(t == null)
			{
				return "NULL";	
			}
			
			if (t == typeof(String) || t == typeof(char))
			{
				return "TEXT";	
			}
			else if(t == typeof(bool))
			{
				return "BOOLEAN";	
			}
			else if (t == typeof(int) || t == typeof(long) || t == typeof(DateTime) || 
			         t == typeof(byte) || t == typeof(sbyte) || t == typeof(short) || 
			         t == typeof(ushort) || t == typeof(uint) || t == typeof(ulong))
			{
				return "INTEGER";	
			}
			else if (t == typeof(decimal))
			{
				return "DECIMAL";	
			}
			else if (t == typeof(double) || t == typeof(float))
			{
				return "REAL";	
			}
			else if (t.IsEnum)
			{
				return "TEXT";	
			}
			else if (t.IsSerializable)
			{
				return "BLOB";	
			}
			else if(t.BaseType == typeof(Entity))
			{
				return "INTEGER";	
			}
			
			throw new ArgumentException("Unable to map property type '" + t.ToString() + "' to Sqlite type");
		}
	}
}
